home *** CD-ROM | disk | FTP | other *** search
- ************************************************************
- * *
- * Guide To Linux Driver Writing -- Character Devices *
- * *
- * or, *
- * *
- * The Wacky World of Driver Development (I) *
- * *
- * Last Revision: Apr 11, 1993 *
- * *
- ************************************************************
-
- This document (C) 1993 Robert Baruch. This document may be freely
- copied as long as the entire title, copyright, this notice, and all of
- the introduction are included along with it. Suggestions, criticisms,
- and comments to baruch@nynexst.com. This document, nor the work
- performed by Robert Baruch using Linux, nor the results of said work
- are connected in any way to any of the Nynex companies. Information
- may settle during transportation. This product should not be used
- in conjunction with a dietary regime except under supervision by your
- doctor.
-
- Right, now that that's over with, let's get into the fun stuff!
-
- ========================
- Introduction
- ========================
-
- There is a companion guide to this Guide, the Linux Character Device
- Tutorial. This tutorial contains working examples of driver code. It
- introduces the reader gently into each aspect of character device driver
- writing through experiments which are carried out by the programmer.
-
- This Guide should serve as a reference to both beginning and advanced
- driver writers.
-
- -=-=-=-=-=-=-
-
- Some words of thanks:
-
- Many thanks to:
-
- Donald J. Becker (becker@metropolis.super.org)
- Don Holzworth (donh@gcx1.ssd.csd.harris.com)
- Michael Johnson (johnsonm@stolaf.edu)
- Karl Heinz Kremer (khk@raster.kodak.com)
- All the driver writers!
-
- ...and of course, Linus "Linux" Torvalds and all the guys who helped
- develop Linux into a BLOODY KICKIN' O/S!
-
- -=-=-=-=-=-=-
-
- ...and now a word of warning:
-
- Messing about with drivers is messing with the kernel. Drivers are run
- at the kernel level, and as such are not subject to scheduling. Further,
- drivers have access to various kernel structures. Before you actually
- write a driver, be *damned* sure of what you are doing, lest you end
- up having to re-format your harddrive and re-install Linux!
-
- The information in this Guide is as up-to-date as I could make it. It also
- has no stamp of approval whatsoever by any of the designers of the kernel.
- I am not responsible for damage caused to anything as a result of using this
- Guide.
-
- ========================
- End of Introduction
- ========================
-
- Kernal-callable functions:
- --------------------------
-
- Note: There is no close for a character device. There is only release.
- See the file data structure below to find out how to determine the number
- of processes which have the device open.
-
- -=-=-=-=-=-=-=-
-
- init : Initializes the driver on bootup.
-
- unsigned long driver_init(unsigned long kmem_start, unsigned long kmem_end)
-
- Arguments: kmem_start -- the start of kernel memory
- kmem_end -- the end of kernel memory
-
- Returns: The new start of kernel memory. This will be different from the
- kmem_start argument if you want to allocate memory for the driver.
-
- The arguments you use depends on what you want to do. Remember that since
- you are going to add your init function to kernel/chr_dev/mem.c, you can
- make your call anything you like, but you have access to the kernel memory
- start and end.
-
- Generally, the init function initializes the driver and hardware, and
- displays some message telling of the availability of the driver and
- hardware. In addition, the register_chrdev function is usually called here.
-
-
- **************
- open : Open a device
-
- static int driver_open(struct inode * inode, struct file * file)
-
- Arguments: inode -- pointer to the inode structure for this device
- file -- pointer to the file structure for this device
-
- Returns: 0 on success,
- -errno on error.
-
- This function is called whenever a process performs open (or fopen) on
- the device special file. If there is no open function for the driver,
- nothing spectacular happens. As long as the /dev file exists, the
- open will succeed.
-
-
- **************
- read : Read from a device
-
- static int driver_read(struct inode * inode, struct file * file,
- char * buffer, int count)
-
- Arguments: inode -- pointer to the inode structure for this device
- file -- pointer to the file structure for this device
- buffer -- pointer to the buffer in user space to read into
- count -- the number of bytes to read
-
- Returns: -errno on error
- >=0 : the number of bytes actually read
-
- If there is no read function for the driver, read calls will return EINVAL.
-
-
- **************
- write : Write to a device
-
- static int driver_write(struct inode * inode, struct file * file,
- char * buffer, int count)
-
- Arguments: inode -- pointer to the inode structure for this device
- file -- pointer to the file structure for this device
- buffer -- pointer to the buffer in user space to write from
- count -- the number of bytes to write
-
- Returns: -errno on error
- >=0 : the number of bytes actually written
-
- If there is no write function for the driver, write calls will return
- EINVAL.
-
-
- **************
- lseek : Change the position offset of the device
-
- static int driver_lseek(struct inode * inode, struct file * file,
- off_t offset, int origin)
-
- Arguments: inode -- pointer to the inode structure for this device
- file -- pointer to the file structure for this device
- offset -- offset from origin to move to (bytes)
- origin -- origin to move from :
- 0 = from origin 0 (beginning)
- 1 = from current position
- 2 = from end
-
- Returns: -errno on error
- >=0 : the position after the move
-
- See Also: Data Structure 'file'
-
- If there is no lseek function for the driver, the kernel will take the default
- seek action, which is to alter the file->f_pos element. For origins of 2,
- the default action results in -EINVAL if file->f_inode is NULL, or it
- sets file->f_pos to file->f_inode->i_size + offset otherwise.
-
-
- **************
- ioctl : Various device-dependent services
-
- static int driver_ioctl(struct inode *inode, struct file *file,
- unsigned int cmd, unsigned long arg)
-
- Arguments: inode -- pointer to the inode structure for this device
- file -- pointer to the file structure for this device
- cmd -- the user-defined command to perform
- arg -- the user-defined argument. You may use this
- as a pointer to user space, since sizeof(long)==
- sizeof(void *).
-
- Returns: -errno on error
- >=0 : whatever you like! (user-defined)
-
- For cmd, FIOCLEX, FIONCLEX, FIONBIO, and FIOASYNC are already defined.
- See the file linux/fs/ioctl.c, sys_ioctl to find out what they do.
- If there is no ioctl call for the driver, and the ioctl command performed
- is not one of the four types listed here, ioctl will return -EINVAL.
-
-
- **************
- select : Performs the select call on the device:
-
- static int driver_select(struct inode *inode, struct file *file,
- int sel_type, select_table * wait)
-
- Arguments: inode -- pointer to the inode structure for this device
- file -- pointer to the file structure for this device
- sel_type -- the select type to perform :
- SEL_IN (read)
- SEL_OUT (write)
- SEL_EX (exception)
- wait -- see the section "Some Notes" for select.
-
- Returns: 0 if the device is not ready to perform the sel_type operation
- !=0 if it is.
-
- See the "Some Notes" section 'way below on information on how to use
- the select call in drivers. If there is no select call for the driver,
- select will act as if the driver is ready for the operation.
-
-
- **************
- release : Release device (no process holds it open)
-
- static void driver_release(struct inode * inode, struct file * file)
-
- Arguments: inode -- pointer to the inode structure for this device
- file -- pointer to the file structure for this device
-
- The release call is activated only when the process closes the device as
- many times as it has opened it. That is, if the process has opened the
- device five times, then only when close is called for the fifth time
- will release be called (that is, provided there were no more calls to open!).
- If there is no release call for the driver, nothing spectacular happens.
-
-
- **************
- readdir : Get the next directory entry
-
- static int driver_readdir(struct inode *inode, struct file *file,
- struct dirent *dirent, int count)
-
- Arguments: inode -- pointer to the inode structure for this device
- file -- pointer to the file structure for this device
- dirent -- pointer to a dirent ("directory entry") structure
- count -- number of entries to read (currently always 1)
-
- Returns: 0 on success
- -errno on failure.
-
- If there is no readdir function for the driver, readdir will return
- -ENOTDIR. This is really for file systems, but you can probably use
- it for whatever you like in a non-fs device, as long as you return
- a dirent structure.
-
- See Also: dirent (data structure)
-
-
- **************
- mmap : Forget this. According to the source (src/linux/mm/mmap.c),
- for character devices only /dev/[k]mem may be mapped.
- Besides, I'm not too clear on what it will do.
-
-
- ----------------------------------------------------------------------
-
- Data structures:
- ----------------
-
- dirent : Information about files in a directory.
-
- #include <linux/dirent.h>
-
- struct dirent {
- long d_ino; /* Inode of file */
- off_t d_off;
- unsigned short d_reclen;
- char d_name[NAME_MAX+1]; /* Name of file */
- };
-
-
- **************
- file : Information about open files
-
- According to the Hacker's Guide to Linux, this structure is mainly used
- for writing filesystems, not drivers. However, there is no reason it
- cannot be used by drivers.
-
- #include <linux/fs.h>
-
- struct file {
- mode_t f_mode;
- dev_t f_rdev; /* needed for /dev/tty */
- off_t f_pos; /* Curr. posn in file */
- unsigned short f_flags; /* The flags arg passed to open */
- unsigned short f_count; /* Number of opens on this file */
- unsigned short f_reada;
- struct inode * f_inode; /* pointer to the inode struct */
- struct file_operations * f_op; /* pointer to the fops struct */
- };
-
-
- **************
- file_operations : Tells the kernel which function to call for
- which kernel function.
-
- #include <linux/fs.h>
-
- struct file_operations {
- int (*lseek) (struct inode *, struct file *, off_t, int);
- int (*read) (struct inode *, struct file *, char *, int);
- int (*write) (struct inode *, struct file *, char *, int);
- int (*readdir) (struct inode *, struct file *, struct dirent *, int);
- int (*select) (struct inode *, struct file *, int, select_table *);
- int (*ioctl) (struct inode *, struct file *, unsigned int,
- unsigned int);
- int (*mmap) (void);
- int (*open) (struct inode *, struct file *);
- void (*release) (struct inode *, struct file *);
- int (*fsync) (struct inode *, struct file *);
- };
-
-
- **************
- inode : Information about the /dev/xxx file (or inode)
-
- #include <linux/fs.h>
-
- struct inode {
- dev_t i_dev;
- unsigned long i_ino; /* Inode number */
- umode_t i_mode; /* Mode of the file */
- nlink_t i_nlink;
- uid_t i_uid;
- gid_t i_gid;
- dev_t i_rdev; /* Device major and minor numbers */
- off_t i_size;
- time_t i_atime;
- time_t i_mtime;
- time_t i_ctime;
- unsigned long i_blksize;
- unsigned long i_blocks;
- struct inode_operations * i_op;
- struct super_block * i_sb;
- struct wait_queue * i_wait;
- struct file_lock * i_flock;
- struct vm_area_struct * i_mmap;
- struct inode * i_next, * i_prev;
- struct inode * i_hash_next, * i_hash_prev;
- struct inode * i_bound_to, * i_bound_by;
- unsigned short i_count;
- unsigned short i_flags; /* Mount flags (see fs.h) */
- unsigned char i_lock;
- unsigned char i_dirt;
- unsigned char i_pipe;
- unsigned char i_mount;
- unsigned char i_seek;
- unsigned char i_update;
- union {
- struct pipe_inode_info pipe_i;
- struct minix_inode_info minix_i;
- struct ext_inode_info ext_i;
- struct msdos_inode_info msdos_i;
- struct iso_inode_info isofs_i;
- struct nfs_inode_info nfs_i;
- } u;
- };
-
- See Also: Driver Calls: MAJOR, MINOR, IS_RDONLY, IS_NOSUID, IS_NODEV,
- IS_NOEXEC, IS_SYNC
-
- -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
-
- Driver calls:
-
-
- **************
- add_timer : Cause a function to be executed when a given amount of time
- has passed.
-
- #include <linux/sched.h>
-
- void add_timer(long jiffies, void (*fn)(void))
-
- Arguments: jiffies -- The number of jiffies to time out after.
- fn -- The function in kernel space to run after timeout.
-
- Note! This is NOT process-specific! If you are looking for a way
- to have a process go to sleep and timeout, look for ?
- Excessive use of this function will cause the kernel to panic if there are
- too many timeouts active at once.
-
-
- **************
- cli : Macro, Prevent interrupts from occuring
-
- #include <asm/system.h>
-
- #define cli() __asm__ __volatile__ ("cli"::)
-
- See Also: sti
-
-
- **************
- free_irq : Free a registered interrupt
-
- #include <linux/sched.h>
-
- void free_irq(unsigned int irq)
-
- Arguments: irq -- the interrupt level to free up
-
- See Also: request_irq
-
-
- **************
- get_fs_byte, get_fs_word, get_fs_long : Get data from user space
-
- Purpose: Allows a driver to access data in user space (which is in
- a different segment than the kernel!)
-
- #include <asm/segment.h>
-
- inline unsigned char get_fs_byte(const char * addr)
- inline unsigned short get_fs_word(const unsigned short *addr)
- inline unsigned long get_fs_long(const unsigned long *addr)
-
- Arguments: addr -- the address in user space to get data from
-
- Returns: the value in user space.
-
- See Also: memcpy_fromfs, memcpy_tofs, put_fs_byte, put_fs_word, put_fs_long
-
-
- **************
- inb, inb_p : Inputs a byte from a port
-
- #include <asm/io.h>
-
- inline unsigned int inb(unsigned short port)
- inline unsigned int inb_p(unsigned short port)
-
- Arguments: port -- the port to input a byte from
-
- Returns: Byte received in the low byte. High byte unused.
-
- See Also: outb, outb_p
-
-
- **************
- IS_RDONLY, IS_NOSUID, IS_NODEV, IS_NOEXEC, IS_SYNC: Macros, check the status
- of the device on the filesystem
-
- #include <linux/fs.h>
-
- #define IS_RDONLY(inode) (((inode)->i_sb) && ((inode)->i_sb->s_flags &
- MS_RDONLY))
- #define IS_NOSUID(inode) ((inode)->i_flags & MS_NOSUID)
- #define IS_NODEV(inode) ((inode)->i_flags & MS_NODEV)
- #define IS_NOEXEC(inode) ((inode)->i_flags & MS_NOEXEC)
- #define IS_SYNC(inode) ((inode)->i_flags & MS_SYNC)
-
-
- **************
- kfree, kfree_s : Free memory which has been kmalloced.
-
- #include <linux/kernel.h>
-
- #define kfree(x) kfree_s((x), 0)
- void kfree_s(void * obj, int size)
-
- Arguments : obj -- pointer to kernel memory you want to free
- size -- size of block you want to free (0 if you don't know
- or are lazy -- slows things down)
-
-
- **************
- kmalloc : Allocate memory in kernel space
-
- #include <linux/kernel.h>
-
- void * kmalloc(unsigned int len, int priority)
-
- Arguments: len -- the length of the memory to allocate. Must not be bigger
- than 4096.
- priority -- GFP_KERNEL or GFP_ATOMIC. GFP_ATOMIC causes kmalloc
- to return NULL if the memory could not be found
- immediately. GFP_KERNEL is the usual priority.
-
- Returns: NULL on failure, a pointer to kernel space on success.
-
-
- **************
- memcpy_fromfs, memcpy_tofs : Copies memory from user(fromfs)/kernel(tofs)
- space to kernel/user space
-
- #include <asm/segment.h>
-
- inline void memcpy_fromfs(void * to, const void * from, unsigned long n)
- inline void memcpy_tofs(void * to, const void * from, unsigned long n)
-
- Arguments: to -- Address to copy data to
- from -- Address to copy data from
- n -- number of bytes to copy
-
- See Also: get_fs_byte, get_fs_word, get_fs_long,
- put_fs_byte, put_fs_word, put_fs_long
-
- Warning! Get the order of arguments right!
-
-
- **************
- MAJOR, MINOR : Macros, get major/minor device number from inode i_dev entry.
-
- #include <linux/fs.h>
-
- #define MAJOR(a) (((unsigned)(a))>>8)
- #define MINOR(a) ((a)&0xff)
-
-
- **************
- outb, outb_p : Outputs a byte to a port
-
- #include <asm/io.h>
-
- inline void outb(char value, unsigned short port)
- inline void outb_p(char value, unsigned short port)
-
- Arguments: value -- the byte to write out
- port -- the port to write it out on
-
- See Also: inb, inb_p
-
-
- **************
- printk : Kernel printf
-
- #include <linux/kernel.h>
-
- int printk(const char *fmt, ...)
-
- Arguments: fmt -- printf-style format
- ... -- var-arguments, printf-style
-
- Returns: Number of characters printed.
-
-
- **************
- put_fs_byte, put_fs_word, put_fs_long : Put data into user space
-
- Purpose: Allows a driver to put a byte, word, or long into user space,
- which is at a different segment than the kernel.
-
- #include <asm/segment.h>
-
- inline void put_fs_byte(char val,char *addr)
- inline void put_fs_word(short val,short * addr)
- inline void put_fs_long(unsigned long val,unsigned long * addr)
-
- Arguments: addr -- the address in user space to get data from
-
- Returns: the value in user space.
-
- See Also: memcpy_fromfs, memcpy_tofs, get_fs_byte, get_fs_word, get_fs_long
-
- Warning! Get the order of arguments right!
-
-
- **************
- register_chrdev : Register a character device with the kernel
-
- #include <linux/fs.h>
- #include <linux/errno.h>
-
- int register_chrdev(unsigned int major, const char *name,
- struct file_operations *fops)
-
- Arguments: major -- the major device number to register as
- name -- the name of the device (currently unused)
- fops -- a file_operations structure for the device.
-
- Returns: -EINVAL if major is >= MAX_CHRDEV (defined in fs.h as 32)
- -EBUSY if major device has already been allocated
- 0 on success.
-
-
- **************
- request_irq : Request to perform a function on an interrupt
-
- #include <linux/sched.h>
- #include <linux/errno.h>
-
- int request_irq(unsigned int irq, void (*handler)(int))
-
- Arguments: irq -- the interrupt to request.
- handler -- the function to handle the interrupt. The interrupt
- handler should be of the form void handler(int).
- Unless you really know what you are doing, don't
- use the int argument.
-
- Returns: -EINVAL if irq>15 or handler==NULL
- -EBUSY if irq is already allocated.
- 0 on success.
-
- See Also: free_irq
-
-
- **************
- select_wait : Add a process to the select-wait queue
-
- #include <linux/sched.h>
-
- inline void select_wait(struct wait_queue ** wait_address, select_table * p)
-
- Arguments: wait_address -- Address of a wait_queue pointer
- p -- Address of a select_table
-
- Devices which use select should define a struct wait_queue pointer and
- initialize it to NULL. select_wait adds the current process to a circular
- list of waits. The pointer to the circular list is wait_address. If
- p is NULL, select_wait does nothing, otherwise the current process is
- put to sleep.
-
- See Also: sleep_on, interruptible_sleep_on, wake_up, wake_up_interruptible
-
-
- **************
- sleep_on, interruptible_sleep_on : Put the current process to sleep.
-
- #include <linux/sched.h>
-
- void sleep_on(struct wait_queue ** p)
- void interruptible_sleep_on(struct wait_queue ** p)
-
- Arguments: q -- Pointer to the driver's wait_queue (see select_wait)
-
- sleep_on puts the current process to sleep in an uninterruptible state.
- That is, signals will not wake the process up. The only thing which
- will wake a process up in this state is a hardware interrupt (which
- would call the interrupt handler of the driver) -- and even then the
- interrupt routine needs to call wake_up to put the process in a running
- state.
-
- interruptible_sleep_on puts the current process to sleep in an interruptible
- state, which means that not only will hardware interrupts get through, but
- also signals and process timeouts ("alarms") will cause the process to
- wake up (and execute interrupt or signal handlers). A call to
- wake_up_interruptible is necessary to wake up the process and allow it
- to continue running where it left off.
-
- See Also: select_wait, wake_up, wake_up_interruptible
-
-
- **************
- sti : Macro, Allow interrupts to occur
-
- #include <asm/system.h>
-
- #define sti() __asm__ __volatile__ ("sti"::)
-
- See Also: cli
-
-
- **************
- sys_getegid, sys_getgid, sys_getpid, sys_getppid, sys_getuid, sys_geteuid :
- Funky functions which get various information about the current process,
-
- #include <linux/sys.h>
-
- int sys_getegid(void)
- int sys_getgid(void)
- int sys_getpid(void)
- int sys_getppid(void)
- int sys_getuid(void)
- int sys_geteuid(void)
-
- sys_getegid gets the effective gid of the process.
- sys_getgid gets the group ID of the process.
- sys_getpid gets the process ID of the process.
- sys_getppid gets the process ID of the process' parent.
- sys_geteuid gets the effective uid of the process.
- sys_getuid gets the user ID of the process.
-
-
- **************
- wake_up, wake_up_interruptible : Wake up _all_ processes waiting on
- the wait queue.
-
- #include <linux/sched.h>
-
- void wake_up(struct wait_queue **q)
- void wake_up_interruptible(struct wait_queue **q)
-
- Arguments: q -- Pointer to the driver's wait_queue (see select_wait)
-
- See Also: select_wait, sleep_on, interruptible_sleep_on
-
-
- --------------------------------------------------------
-
- ==========
- Some notes
- ==========
-
- Interrupts, Drivers, and You!
- -----------------------------
-
- First, a brief exposition on the Meaning of Interrupts. There are three
- ways by which a program running in the CPU may be interrupted. The first is
- the external interrupt. This is caused by an external device (that is,
- external to the CPU) signalling for attention. These are referred to as
- "interrupt requests" or "IRQs".
-
- The second method is the exception, which is caused by something internal to
- the CPU, usually in response to a condition generated by execution of an
- instruction.
-
- The third method is the software interrupt, which is a deliberately executed
- interrupt -- the INT instruction in assembly. System calls are implemented
- using software interrupts; when a system call is desired, Linux places the
- system call number in EAX, and performs an INT 0x80 instruction.
-
- Since drivers usually deal with hardware devices, it is logical that driver
- interrupts should refer to external interrupts. There are 16 available IRQs
- -- IRQ0 through IRQ15. The following table lists the official uses of the
- various IRQs:
-
- IRQ0 -- timer 0
- IRQ1 -- keyboard
- IRQ2 -- AT slave 8259 ("cascade")
- IRQ3 -- COM2
- IRQ4 -- COM1
- IRQ5 -- LPT2
- IRQ6 -- floppy
- IRQ7 -- LPT1
- IRQ8-12 ??????
- IRQ13 -- coprocessor error
- IRQ14,15 ??????
-
- Writing drivers which can be interrupted requires care. Be aware that
- every line you write can be interrupted, and thus cause variable
- changes to occur. If you really want to protect critical sections from
- being interrupted, use the cli() and sti() driver calls.
-
- Suppose you wanted to test some kind of funky condition, where success of
- the condition leads to going to sleep, and being woken up by an interrupt.
- Consider this code:
-
- void driver_interrupt(int unused)
- {
- if (!driver_stuff.int_flag) return; /* Spurious interrupts
- are not unheard of */
- driver_stuff.int_flag=0;
- weird_wacky(); /* Do some weird and wacky stuff
- here to handle the interrupt */
- disable_ints(); /* Disable the device from issuing interrupts */
- wake_up(&driver_stuff.wait_queue); /* Sets process to TASK_RUNNING */
- }
-
- if (conditions_are_ripe())
- {
- driver_stuff.int_flag = 1;
- enable_ints(); /* Enable device to interrupt us */
- sleep_on(&driver_stuff.wait_queue); /* Sets process to TASK_UNINTERRUPTIBLE */
- }
-
- Assume we just leave the conditions_are_ripe code, determining that the
- conditions are ripe! We have just enabled the device to interrupt the
- machine. So we are now about to enter the sleep_on code, and
- what should happen but the pesky device issues an interrupt. Ka-chunk! and
- we enter the driver_interrupt routine, which does some weird and wacky
- stuff to handle the interrupt, and then we disable the device's interrupts.
- Ka-ching! we enter the wake_up function which sets the process up to run again.
- Boink! we exit the interrupt handler and commence where we left off
- (just about to enter the sleep_on code). Vooosh! we're now sleeping the
- process, awaiting an interrupt which will never occur, since the interrupt
- handler disabled the device from interrupts! What to do?
-
- Use cli() and sti() to protect the critical sections of code:
-
- cli();
- if (conditions_are_ripe())
- {
- driver_stuff.int_flag = 1;
- enable_ints(); /* Enable device to interrupt us */
- sleep_on(&driver_stuff.wait_queue); /* Sets process to TASK_UNINTERRUPTIBLE */
- }
- else sti();
-
- First we clear interrupts. This is not the same as disabling device
- interrupts! This actually prevents a hardware interrupt from causing the
- CPU to execute interrupt code. In effect, the interrupt is deferred.
-
- Now we can do our check and perform sleep_on, secure in the knowledge that
- the interrupt handler cannot be called. The sleep_on (and interruptible_
- sleep_on) call has a sti() in it in the right place, so you don't have to
- worry about calling sti() before sleep_on, and running into a race condition
- again.
-
- Of course, with any interruptible device driver, you must be careful never
- to spend too much time in the interrupt routine if you are expecting more
- than one interrupt, because you may miss your second interrupt.
-
- -=-=-=-=-=-=-
-
- Drivers and signals:
- --------------------
-
- When a process is sleeping in an interruptible state, any signal can wake it
- up. This is the sequence of events which occurs when a sleeping process
- receives a signal:
-
- (1) Set current->signal.
- (2) Set the process to a runnable state.
- (3) Execute the rest of the driver call.
- (4) Run the signal handler.
- (5) If the driver call in step 3 returned -ERESTARTNOHAND or -ERESTARTNOINTR,
- then return from the driver call with EINTR. If the driver call in step
- 3 returned -ERESTARTSYS, then restart the driver call. Otherwise, just
- return with whatever was returned from the driver call.
-
- In the driver, you can tell if a sleep has been interrupted by a signal
- with the following code:
-
- if (current->signal & ~current->blocked)
- {
- /* Do things based on sleep interrupted by signal */
- }
-
- -=-=-=-=-=-=-
-
- Drivers and timeouts:
- ---------------------
-
- Suppose you wanted to sleep on an interrupt, but also time out after
- a period of time. You could always use the add_timer, but that's
- frowned upon because there are only a limited number of timers
- available -- currently there are 64.
-
- The usual solution is to manually alter the current process's timeout:
-
- current->timeout = jiffies + X;
- interruptible_sleep_on(&driver_stuff.wait_queue);
-
- (Interruptible sleep_on must be used here to allow a timeout to interrupt
- the sleep). This will cause the scheduler to set the task running again when
- X jiffies has gone by. Even if the timeout goes off and the process is
- allowed to continue running, it is probably a good idea to call
- wake_up_interruptible in case the process needs to be rescheduled.
-
- To find out if it was a timeout which caused the process to wake up,
- check current->timeout. If it is 0, a timeout occurred. Otherwise it
- should remain what you set it at. If a timeout did not occur, and something
- else woke the process up, you should set current->timeout to 0 to prevent
- the timeout from continuing.
-
- The disadvantage of this method is that the process can only have one
- timeout at a time. Over *all* drivers.
-
-
- -=-=-=-=-=-=-
-
- The driver_select call:
- -----------------------
-
- When a process issues a select call, it is checking to see if the given
- devices are ready to perform the given operations. For example, suppose
- you want a driver to have a command written to it, and to disallow further
- commands until the current command is complete. Well, in the write call
- you would block commands if there is already a command operating (for example,
- waiting for a board to do something). But that would require the process to
- write over and over again until it succeeds. That just burns cycles.
-
- The select call allows a process to determine the availability of read and
- write. In the above example, one merely has to select for write on that
- device's file descriptor (as returned by open), and the process would be
- put to sleep until the device is ready to be written to.
-
- The kernel will call the driver's driver_select call when the process issues a
- select call. The arguments to the driver_select call are detailed above.
- If the wait argument is non-NULL, and there is no error condition caused
- by the select, driver_select should put the process to sleep, and arrange
- to be woken up when the device becomes ready (usually through an interrupt).
-
- If, however, the wait argument is NULL, then the driver should quickly
- see if the device is ready, and return even if it is not. The select_wait
- function does this already for you (see further).
-
- Putting the process to sleep does not require calling a sleep_on function.
- It is the select_wait function which is called, with the p argument being
- equal to the wait argument passed to driver_select.
-
- select_wait is pretty much equivalent to interruptible_sleep_on in that it
- adds the current process to the wait queue and sleeps the process in
- an interruptible state. The internals of the differences between
- select_wait and interruptible_sleep_on are relatively irrelevant here.
- Suffice it to say that to wake the process up from the select, one needs
- to perform the wake_up_interruptible call. When that happens, the
- process is free to run.
-
- However, in the case of interruptible_sleep_on, the process will continue
- running after the call to interruptible_sleep_on. In the case of select_wait,
- the process does not. driver_select is called as a "side effect" of the
- select call, and so completes even when it calls select_wait. It is
- not select_wait which sleeps the process, but the select call. Nevertheless,
- it is required to call select_wait to add the process to the wait-queue,
- since select will not do that.
-
- All one needs to remember for driver_select is:
-
- (1) Call select_wait if the device is not ready, and return 0.
- (2) Return 1 if the device is ready.
-
-
- Calling select with a timeout is really no different to the driver than
- calling it without select. But there is one crucial difference. Remember
- timing out on interrupts above? Well, interrupt timeouts and select timeouts
- cannot co-exist. They both use current->timeout to wake the process up
- after a period of time. Remember that!
-
-
- -=-=-=-=-=-=-
-
- Installation notes:
- -------------------
-
- Before you sit down and write your first driver, first make sure you
- understand how to recompile the kernel. Then go ahead and recompile it!
- Recompilation of the kernel is described in the FAQ. If you can't
- recompile the kernel, you can't install your driver into the kernel.
- [Although I hear tell of a package on sunsite which can load and unload
- drivers while the kernel is running. Until I test out this package,
- I won't include instructions for it here.]
-
- For character devices, you need to go into the mem.c file in the
- (source)/linux/kernel/chr_dev directory, to the chr_dev_init function,
- and add your init function to it. Recompile the kernel, and away you go!
-
- (BTW, would you manually have to do a mknod to make the /dev/xxx entry
- for your driver? Can you do it in the init function?)
-
- In general, one installs a device special file in /dev manually, by using
- mknod:
-
- mknod /dev/xxx c major minor
-
- If you registered your character driver as major device X, then all accesses
- to /dev/xxx where major==X will call your driver functions.
-